package com.shareware.common.memory

import android.app.ActivityManager
import android.content.Context
import android.os.Build
import android.os.Debug
import android.os.Process
import android.text.TextUtils
import com.shareware.common.DEFAULT
import com.shareware.common.getContextGlobal
import com.shareware.common.memory.entity.MemInfo
import com.shareware.common.memory.entity.ProcessInfo
import com.shareware.common.memory.entity.PssInfo
import com.shareware.common.memory.entity.SMAPSInfo
import com.shareware.common.memory.entity.SystemInfo
import com.shareware.common.utils.log
import com.shareware.common.utils.safeApply
import java.io.File
import java.util.regex.Matcher
import java.util.regex.Pattern

/**
 * desc: 功能描述
 * copy from tencent matrix
 * email: mobiledeveloper@qq.com
 */
object MemoryHelper {
    val activityManager =
        getContextGlobal().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager

    val memClass = activityManager.memoryClass
    val largeMemClass = activityManager.largeMemoryClass

    fun getAllProcessPss(): Array<MemInfo> {
        val begin = System.currentTimeMillis()
        val memInfoArray = prepareAllProcessInfo()
        val pidMemInfoArray = activityManager.getProcessMemoryInfo(memInfoArray.toPidArray())
        if (pidMemInfoArray != null) {
            for (i in memInfoArray.indices) {
                if (pidMemInfoArray[i] == null) {
                    memInfoArray[i].amsPssInfo = PssInfo(0, 0, 0, 0, 0, 0, 0, 0, 0)
                    continue
                }
                memInfoArray[i].amsPssInfo = generatePssInfo(pidMemInfoArray[i])
            }
        }
        log(DEFAULT, "getAllProcessPss cost: ${System.currentTimeMillis() - begin}")
        return memInfoArray
    }

    fun getCurrentProcessMemInfo(): MemInfo {
        val begin = System.currentTimeMillis()
        return MemInfo().also { it.cost = System.currentTimeMillis() - begin }
    }

    fun getCurrentProcessMemInfoWithPss(): MemInfo {
        val begin = System.currentTimeMillis()
        return MemInfo(debugPssInfo = getPssInfoFromDebug()).also {
            it.cost = System.currentTimeMillis() - begin
        }
    }

    fun getCurrentProcessMemInfoWithAmsPss(): MemInfo {
        val begin = System.currentTimeMillis()
        return MemInfo(amsPssInfo = getPssInfoFromAms()).also {
            it.cost = System.currentTimeMillis() - begin
        }
    }

    fun getCurrentProcessFullMemInfo(): MemInfo {
        val begin = System.currentTimeMillis()
        return MemInfo(amsPssInfo = getPssInfoFromAms(), debugPssInfo = getPssInfoFromDebug())
            .also { it.cost = System.currentTimeMillis() - begin }
    }

    private fun Array<MemInfo>.toPidArray(): IntArray {
        val pidArray = IntArray(size)
        forEachIndexed { i, info ->
            pidArray[i] = info.processInfo!!.pid // processInfo must be not null
        }
        return pidArray
    }

    private fun prepareAllProcessInfo(): Array<MemInfo> {
        val processInfoList = activityManager.runningAppProcesses
        val memoryInfoList: MutableList<MemInfo> = ArrayList()

        if (processInfoList == null) {
            log(DEFAULT, "error: activityManager.runningAppProcesses - no running process")
            return emptyArray()
        }
        val systemInfo = SystemInfo.get()
        for (i in processInfoList.indices) {
            val processInfo = processInfoList[i]
            val pkgName = getContextGlobal().packageName
            if (Process.myUid() != processInfo.uid
                || TextUtils.isEmpty(processInfo.processName)
                || !processInfo.processName.startsWith(pkgName)
            ) {
                continue
            }
            memoryInfoList.add(
                MemInfo(
                    processInfo = ProcessInfo(processInfo.pid, processInfo.processName),
                    statusInfo = null,
                    javaMemInfo = null,
                    nativeMemInfo = null,
                    systemInfo = systemInfo,
                )
            )
        }
        return memoryInfoList.toTypedArray()
    }

    private fun generatePssInfo(memoryInfo: Debug.MemoryInfo): PssInfo {
        return PssInfo().also {
            it.totalPssK = memoryInfo.totalPss
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                memoryInfo.memoryStats.apply {

                    fun Map<String, String>.getInt(key: String) = get(key)?.toInt() ?: -1
                    //堆内存
                    it.pssJavaK = getInt("summary.java-heap")
                    //native层的so中调用malloc或new创建的内存
                    it.pssNativeK = getInt("summary.native-heap")
                    //dex+so相关代码占用内存
                    it.pssCodeK = getInt("summary.code")
                    //线程栈
                    it.pssStackK = getInt("summary.stack")
                    //OpenGL和Surface相关内存
                    it.pssGraphicK = getInt("summary.graphics")
                    it.pssPrivateOtherK = getInt("summary.private-other")
                    it.pssSystemK = getInt("summary.system")
                    it.pssSwapK = getInt("summary.total-swap")
                }
            } else {
                memoryInfo.apply {
                    it.pssJavaK = dalvikPrivateDirty
                    it.pssNativeK = nativePrivateDirty
                    it.pssSystemK = totalPss - totalPrivateClean - totalPrivateDirty
                }
            }
        }
    }

    fun getPssInfoFromDebug(): PssInfo {
        val dbgInfo = Debug.MemoryInfo()
        Debug.getMemoryInfo(dbgInfo)
        return generatePssInfo(dbgInfo)
    }

    fun getPssInfoFromAms(): PssInfo {
        val mi = activityManager.getProcessMemoryInfo(arrayOf(Process.myPid()).toIntArray())
            .firstOrNull()
        return if (mi != null) {
            generatePssInfo(mi)
        } else {
            PssInfo()
        }
    }

    fun getSMAPSInfoString(pid: Int = Process.myPid()): String {
        val list = getSMAPSInfo(pid)
        // PSS RSS SIZE SWAP_PSS SH_C SH_D PRI_C PRI_D COUNT PERM NAME
        val format = "%8s %8s %8s %8s %8s %8s %8s %8s %8s %8s     %s"
        val sb = StringBuilder()
        sb.append("\n")
        sb.append(
            String.format(
                format,
                "PSS",
                "RSS",
                "SIZE",
                "SWAP_PSS",
                "SH_C",
                "SH_D",
                "PRI_C",
                "PRI_D",
                "COUNT",
                "PERM",
                "NAME"
            )
        ).append("\n")
        list.forEach {
            sb.append(
                String.format(
                    format,
                    it.pss,
                    it.rss,
                    it.vmSize,
                    it.swapPss,
                    it.sharedClean,
                    it.sharedDirty,
                    it.privateClean,
                    it.privateDirty,
                    it.count,
                    it.permission,
                    it.name
                )
            ).append("\n")
        }
        return sb.toString()
    }

    fun getSMAPSInfo(pid: Int = Process.myPid()): List<SMAPSInfo> {
        val pattern =
            Pattern.compile("^[0-9a-f]+-[0-9a-f]+\\s+([rwxps-]{4})\\s+\\d+\\s+\\d+:\\d+\\s+\\d+\\s+(.*)$")
        val merged: HashMap<String, SMAPSInfo> = HashMap()
        var currentInfo: SMAPSInfo? = null

        safeApply {
            File("/proc/${pid}/smaps").reader().forEachLine { line ->
                currentInfo?.let {
                    var found = true
                    when {
                        //是进程使用内存空间，并不一定实际分配了物理内存
                        line.startsWith("Size:") -> {
                            val sizes = line.substring("Size:".length).trim { it <= ' ' }
                                .split(" ").toTypedArray()
                            currentInfo!!.vmSize += sizes[0].toLong()
                        }
                        //实际使用物理内存（包含共享库占用的内存）,有对应的物理页面
                        //Rss=Shared_Clean+Shared_Dirty+Private_Clean+Private_Dirty
                        line.startsWith("Rss:") -> {
                            val sizes =
                                line.substring("Rss:".length).trim { it <= ' ' }.split(" ")
                                    .toTypedArray()
                            currentInfo!!.rss += sizes[0].toLong()
                        }
                        //实际使用的物理内存（按比例包含共享库占用的内存）
                        //进程X的PSS = 进程X自身独自占用的内存 +共享库所占用的内存／使用共享库的进程数
                        line.startsWith("Pss:") -> {
                            val sizes =
                                line.substring("Pss:".length).trim { it <= ' ' }.split(" ")
                                    .toTypedArray()
                            currentInfo!!.pss += sizes[0].toLong()
                        }
                        //多个进程共享，是干净页面，表示该页面未被修改
                        line.startsWith("Shared_Clean:") -> {
                            val sizes =
                                line.substring("Shared_Clean:".length).trim { it <= ' ' }
                                    .split(" ").toTypedArray()
                            currentInfo!!.sharedClean += sizes[0].toLong()
                        }
                        //多个进程共享，是脏页,表示该页面被修改过，发生换页时回写到磁盘
                        line.startsWith("Shared_Dirty:") -> {
                            val sizes =
                                line.substring("Shared_Dirty:".length).trim { it <= ' ' }
                                    .split(" ").toTypedArray()
                            currentInfo!!.sharedDirty += sizes[0].toLong()
                        }
                        //进程独占，是干净页面，表示该页面未被修改
                        line.startsWith("Private_Clean:") -> {
                            val sizes =
                                line.substring("Private_Clean:".length).trim { it <= ' ' }
                                    .split(" ").toTypedArray()
                            currentInfo!!.privateClean += sizes[0].toLong()
                        }
                        //进程独占，是脏页，表示该页面被修改过，发生换页时回写到磁盘
                        line.startsWith("Private_Dirty:") -> {
                            val sizes =
                                line.substring("Private_Dirty:".length).trim { it <= ' ' }
                                    .split(" ").toTypedArray()
                            currentInfo!!.privateDirty += sizes[0].toLong()
                        }

                        line.startsWith("SwapPss:") -> {
                            val sizes = line.substring("SwapPss:".length).trim { it <= ' ' }
                                .split(" ").toTypedArray()
                            currentInfo!!.swapPss += sizes[0].toLong()
                        }

                        else -> {
                            found = false
                        }
                    }
                    if (found) return@forEachLine
                }

                val matcher: Matcher = pattern.matcher(line)
                if (matcher.find()) {
                    val permission = matcher.group(1)
                    val name = matcher.group(2)
                    currentInfo = merged["$permission|$name"]
                    if (currentInfo == null) {
                        currentInfo = SMAPSInfo()
                        currentInfo!!.let {
                            merged["$permission|$name"] = it
                            it.permission = permission
                            it.name = name
                        }
                    }
                    currentInfo!!.count++
                }
            }
        }
        val list = ArrayList(merged.values)
        list.sortWith { o1, o2 -> ((o2.pss - o1.pss).toInt()) }
        return list
    }
}